﻿using System;
using System.Net;
using System.Net.Sockets;

namespace tracert
{
  public class Prog
  {
    public static void Main(string[] args)
    {
      if (args.Length == 0)
      {
        Console.WriteLine("USAGE: tracert <address>");
        return;
      }

      string result = Trace(args[0]);
      Console.WriteLine(result);
      Console.ReadKey();
    }

    private static string Trace(string address)
    {
      Socket socket;
      try
      {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
      }
      catch
      {
        return "Error: raw sockets requires elevation";
      }

      IPAddress target;
      try
      {
        IPHostEntry addr = Dns.GetHostEntry(address);
        target = addr.AddressList[0];
      }
      catch
      {
        return string.Format("Host not found: {0}", address);
      }

      Console.WriteLine("Tracing route to [{0}]\nover a maximum of 30 hops:\n", target);
      socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 1000);

      byte[] bytes = CreatePingPacket();
      var targetEp = new IPEndPoint(target, 0);

      socket.Ttl = 1;
      var msg = new byte[256];
      EndPoint remoteEp = new IPEndPoint(IPAddress.Any, 0);

      while (true)
      {
        DateTime startTime = DateTime.Now;
        socket.SendTo(bytes, targetEp);
        Console.Write("  {0}:\t", socket.Ttl);
        try
        {
          socket.ReceiveFrom(msg, ref remoteEp);
        }
        catch
        {
          Console.WriteLine("Request timeout");
          socket.Ttl++;
          continue;
        }

        Console.WriteLine("{0} ms from {1}", (DateTime.Now - startTime).Milliseconds, remoteEp);
        if (remoteEp.Equals(targetEp) || socket.Ttl == 30) return "\nTrace complete.";
        socket.Ttl++;
      }
    }

    private static byte[] CreatePingPacket()
    {
      var header = new ICMPHeader();
      header.Type = 8; // ICMP_ECHO

      var pingMessage = new Byte[32];
      for (int i = 0; i < pingMessage.Length; i++)
      {
        pingMessage[i] = (byte)'#';
      }
     
      byte[] bytes = header.GetProtocolPacketBytes(pingMessage);
      return bytes;
    }
  }
}